package com.orion.service.rhlc;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.orion.entity.Building;
import com.orion.entity.Car;
import com.orion.entity.Comment;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.MinAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;

/**
 * @author Administrator
 */
@Service
public class RhlcEsService implements IRhlcEsService{


    @Resource(name = "highClient")
    private RestHighLevelClient highClient;

    @Resource(name = "objectMapper")
    private ObjectMapper objectMapper;

    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    /**
     * 简单演示，用代码创建具体的索引非常麻烦，建议还是使用DSL编写执行
     *
     * @param idx
     * @param numberOfShards
     * @param numberOfReplicas
     * @return
     * @throws IOException
     */
    @Override
    public CreateIndexResponse createIndex(String idx, Integer numberOfShards, Integer numberOfReplicas) throws IOException {
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(idx);
        String jsonStr = "{\n" +
                "  \"settings\":{\n" +
                "    \"number_of_replicas\":2,\n" +
                "    \"number_of_shards\":1\n" +
                "  },\n" +
                "  \"mappings\":{\n" +
                "    \"properties\":{\n" +
                "      \"price\":{\"type\":\"double\"},\n" +
                "      \"color\":{\"type\":\"keyword\"},\n" +
                "      \"make\":{\"type\":\"keyword\"},\n" +
                "      \"sold\":{\"type\":\"date\",\"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis\"},\n" +
                "      \"remark\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\"}\n" +
                "    }\n" +
                "  }\n" +
                "}";

        createIndexRequest.source(jsonStr, XContentType.JSON);
        CreateIndexResponse createIndexResponse = highClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        return createIndexResponse;
    }

    @Override
    public GetIndexResponse indexInfo(String idx) throws IOException {
        GetIndexRequest getIndexRequest = new GetIndexRequest(idx);
        return highClient.indices().get(getIndexRequest, RequestOptions.DEFAULT);
    }

    @Override
    public AcknowledgedResponse deleteIndex(String idx) throws IOException {
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(idx);
        return highClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
    }

    /**
     * 根据id查询doc
     *
     * @param idx
     * @param id
     * @return
     * @throws IOException
     */
    @Override
    public GetResponse queryDocById(String idx, Long id) throws IOException {
        GetRequest getRequest = new GetRequest(idx, id.toString());
        return highClient.get(getRequest, RequestOptions.DEFAULT);
    }

    /**
     * upsert 对于传输的参数，存在就更新，不存在就不更新
     *
     * @param idx
     * @param building
     * @return
     * @throws IOException
     */
    @Override
    public UpdateResponse update(String idx, Building building) throws IOException {
        UpdateRequest updateRequest = new UpdateRequest(idx,building.getId().toString());
        String jsonStr = objectMapper.writeValueAsString(building);
        updateRequest.doc(jsonStr,XContentType.JSON);
        return highClient.update(updateRequest, RequestOptions.DEFAULT);
    }

    @Override
    public DeleteResponse delete(String idx, Long id) throws IOException{
        DeleteRequest deleteRequest = new DeleteRequest(idx);
        deleteRequest.id(id.toString());
        DeleteResponse deleteResponse = highClient.delete(deleteRequest, RequestOptions.DEFAULT);
        return deleteResponse;
    }

    /**
     * 多次提交，新增或覆盖，version会增加
     *
     * @param idx
     * @param building
     * @return
     * @throws IOException
     */
    @Override
    public IndexResponse addDoc(String idx, Building building) throws IOException {
        IndexRequest indexRequest = new IndexRequest();
        indexRequest.index(idx);
        String jsonStr = objectMapper.writeValueAsString(building);
        indexRequest.source(jsonStr, XContentType.JSON);
        indexRequest.id(building.getId().toString());
        return highClient.index(indexRequest, RequestOptions.DEFAULT);
    }

    /**
     * GET /building/_doc/_search
     * {
     * 	"from": 0,
     * 	"highlight": {
     * 		"fields": {
     * 			"name": {
     * 				"post_tags": ["</font>"],
     * 				"pre_tags": ["<font color='read'"]
     *                        }* 		}
     *    },
     * 	"query": {
     * 		"bool": {
     * 			"must": [{
     * 				"match": {
     * 					"name": {
     * 						"query": "广东"
     *                    }
     *                }
     *            }],
     * 			"should": [{
     * 				"range": {
     * 					"history_years": {
     * 						"lt": 17
     *                    }
     *                }
     *            }, {
     * 				"range": {
     * 					"create_time": {
     * 						"gte": "2007-06-01"
     *                    }
     *                }
     *            }]
     *        }
     *    },
     * 	"size": 20,
     * 	"sort": [{
     * 		"create_time": {
     * 			"order": "desc"
     *        }
     *    }, {
     * 		"history_years": {
     * 			"order": "asc"
     *        }
     *    }]
     * }
     * <p>
     * 深分页
     *
     * @param idx
     * @param pageSize
     * @param from
     * @param building
     * @return
     */
    @Override
    public SearchResponse queryDeepPage(String idx, int pageSize, int from, Building building) throws IOException {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.matchQuery("name",building.getName()));
        boolQueryBuilder.should(QueryBuilders.rangeQuery("history_years").lt(building.getHistoryYears()));
        boolQueryBuilder.should(QueryBuilders.rangeQuery("create_time").gte(building.getCreateTime()));
        searchSourceBuilder.query(boolQueryBuilder);

        searchSourceBuilder.from(from);
        searchSourceBuilder.size(pageSize);

        FieldSortBuilder createTimeSort = SortBuilders.fieldSort("create_time").order(SortOrder.DESC);
        FieldSortBuilder historyYearsSort = SortBuilders.fieldSort("history_years").order(SortOrder.ASC);
        searchSourceBuilder.sort(Arrays.asList(createTimeSort,historyYearsSort));

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");
        highlightBuilder.preTags("<font color='red'>");
        highlightBuilder.postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);


        SearchRequest searchRequest = new SearchRequest(idx);
        searchRequest.source(searchSourceBuilder);

        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     * GET /building/_doc/_search
     * {
     *   "query": {
     *     "bool": {
     *       "should": [
     *         {
     *           "bool": {
     *             "must": [
     *               {"match": {"name": "广东"}},
     *               {"range": {"history_years": {"gt": 4}}}
     *               ]
     *             ,
     *             "must_not": {
     *               "range": {"create_time": {"lte": "2007-01-01"}}
     *             }
     *           }
     *         }
     *         ,
     *         {
     *           "term": {"shortname": "滕王阁"}
     *         }
     *       ]
     *     }
     *   }
     * }
     * 组合bool查询,bool嵌套bool
     *
     * @param idx
     * @param building
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse queryComposite(String idx, Building building) throws IOException {
        MatchQueryBuilder must_match_name = QueryBuilders.matchQuery("name", building.getName());
        RangeQueryBuilder must_range_historyYears = QueryBuilders.rangeQuery("history_years").gt(building.getHistoryYears());
        RangeQueryBuilder mustNot_range_createTime = QueryBuilders.rangeQuery("create_time").lte(building.getCreateTime());


        BoolQueryBuilder shouldBool = QueryBuilders.boolQuery();
        shouldBool.must(must_match_name);
        shouldBool.must(must_range_historyYears);
        shouldBool.mustNot(mustNot_range_createTime);

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.should(QueryBuilders.termQuery("shortname",building.getShortname()));
        boolQueryBuilder.should(shouldBool);

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(boolQueryBuilder);

        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(idx);
        searchRequest.source(searchSourceBuilder);

        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     * 简单聚合
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse aggs(String idx) throws IOException {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(new TermsAggregationBuilder("my_group_make").field("make"));

        SearchRequest searchRequest = new SearchRequest(idx);
        searchRequest.source(searchSourceBuilder);

        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     * GET /cars/_doc/_search
     * {
     *   "aggs":{
     *     "my_group_make":{
     *       "terms":{"field":"make"},
     *       "aggs":{
     *         "my_avg_price":
     *           {"avg":{"field":"price"}}
     *       }
     *     }
     *   }
     * }
     * 多个聚合，父子聚合,分组+统计
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse aggsComposite(String idx) throws IOException {
        SearchRequest searchRequest = new SearchRequest(idx);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        AggregationBuilder groupMakeAggs = AggregationBuilders.terms("my_group_make").field("make");
        groupMakeAggs.subAggregation(AggregationBuilders.avg("my_avg_price").field("price"));

        searchSourceBuilder.aggregation(groupMakeAggs);
        searchRequest.source(searchSourceBuilder);
        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     *GET /cars/_doc/_search
     * {
     *   "size":0,
     *   "aggs": {
     *     "my_gourp_make": {
     *       "terms":{"field": "make"},
     *       "aggs":{
     *         "my_group_color":{
     *           "terms":{"field":"color"}
     *         }
     *       }
     *     }
     *   }
     * }
     * 父子聚合，分组+分组
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse aggsMultiGroup(String idx) throws IOException {
        SearchRequest searchRequest = new SearchRequest(idx);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        TermsAggregationBuilder aggregation = AggregationBuilders.terms("my_gourp_make").field("make")
                .subAggregation(AggregationBuilders.terms("my_group_color").field("color"));

        searchSourceBuilder.size(0).aggregation(aggregation);
        searchRequest.source(searchSourceBuilder);
        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     * GET /cars/_doc/_search
     * {
     *   "size":0,
     *   "aggs":{
     *     "my_group_make":{
     *       "terms":{"field":"make"},
     *       "aggs":{
     *         "my_max_price":{"max":{"field":"price"}},
     *         "my_group_color":{"terms":{"field":"color"}}
     *       }
     *     }
     *   }
     * }
     * 以make分组统计其最大价格以及车的颜色分组数量,最贵和颜色是等级关系
     */
    @Override
    public SearchResponse aggsBros(String idx) throws IOException {
        SearchRequest searchRequest = new SearchRequest(idx);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        TermsAggregationBuilder termsMake = AggregationBuilders.terms("my_group_make").field("make");
        MaxAggregationBuilder maxPrice = AggregationBuilders.max("my_max_price").field("price");
        TermsAggregationBuilder termsColor = AggregationBuilders.terms("my_group_color").field("color");

        termsMake.subAggregation(maxPrice).subAggregation(termsColor);
        searchSourceBuilder.size(0).aggregation(termsMake);
        searchRequest.source(searchSourceBuilder);
        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }


    /**
     * GET /cars/_doc/_search
     * {
     *   "size":0,
     *   "aggs":{
     *     "my_group_color":{
     *       "terms":{
     *         "field":"color"
     *       },
     *       "aggs":{
     *         "my_avg_price":{
     *             "avg":{
     *               "field":"price"
     *             }
     *         },
     *         "my_group_make":{
     *           "terms":{
     *             "field":"make"
     *           },
     *           "aggs":{
     *             "max_price":{"max":{"field":"price"}},
     *             "min_price":{"min":{"field":"price"}}
     *           }
     *         }
     *       }
     *     }
     *   }
     * }
     * #####复合版,先颜色分组，在求得各颜色的平均加钱，（平级关系）各个颜色的品牌的最大最小价格
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse complexAggs(String idx) throws IOException {
        SearchRequest searchRequest = new SearchRequest(idx);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.size(0);

        TermsAggregationBuilder termsColor = AggregationBuilders.terms("my_group_color").field("color");
        AvgAggregationBuilder avg = AggregationBuilders.avg("my_avg_price").field("price");
        termsColor.subAggregation(avg);

        TermsAggregationBuilder termsMake = AggregationBuilders.terms("my_group_make").field("make");
        MaxAggregationBuilder max = AggregationBuilders.max("max_price").field("price");
        MinAggregationBuilder min = AggregationBuilders.min("min_price").field("price");
        termsMake.subAggregation(max).subAggregation(min);

        termsColor.subAggregation(termsMake);

        searchSourceBuilder.aggregation(termsColor);

        searchRequest.source(searchSourceBuilder);
        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     *
     * GET /cars/_doc/_search
     * {
     *   "query": {
     *     "term": {
     *       "make": "honda"
     *     }
     *   },
     *   "aggs": {
     *     "filterAndAggs": {
     *       "filter": {
     *         "range": {
     *           "sold": {
     *             "from": "2014-11-01"
     *           }
     *         }
     *       },
     *       "aggs": {
     *         "totalSum": {
     *           "sum": {
     *             "field": "price"
     *           }
     *         }
     *       }
     *     }
     *   }
     * }
     * #####过滤、搜索、聚合,先查出honda车，在选出2014-11-01后sold的车的总销售额
     */
    @Override
    public SearchResponse aggsFilterQuery(String idx, Car car) throws IOException {
        SearchRequest searchRequest = new SearchRequest(idx);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(QueryBuilders.termQuery("make",car.getMake()));

        FilterAggregationBuilder filterAggs = AggregationBuilders.filter("filterAndAggs"
                , QueryBuilders.rangeQuery("sold").from(dtf.format(car.getSold())));

        filterAggs.subAggregation(AggregationBuilders.sum("totalSum").field("price"));

        searchSourceBuilder.aggregation(filterAggs);
        searchRequest.source(searchSourceBuilder);
        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     * 建议搜索RHLC
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse suggest(String idx, String prefix) throws IOException {
        SearchRequest searchRequest = new SearchRequest(idx);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("s-text"
                , SuggestBuilders.completionSuggestion("suggest").prefix(prefix));
        searchSourceBuilder.suggest(suggestBuilder);
        searchRequest.source(searchSourceBuilder);
        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }

    /**
     *
     * #####嵌套查询与嵌套聚合
     * GET /nested_test/_doc/_search
     * {
     *   "query": {
     *     "bool": {
     *       "must": {
     *         "nested": {
     *           "path": "comments",
     *           "query": {
     *             "bool": {
     *               "must": [
     *                 {
     *                   "match": {
     *                     "comments.name": "alice"
     *                   }
     *                 },
     *                 {
     *                   "match": {
     *                     "comments.age": 28
     *                   }
     *                 }
     *               ]
     *             }
     *           }
     *         }
     *       }
     *     }
     *   },
     *   "aggs": {
     *     "mycomments": {
     *       "nested": {
     *         "path": "comments"
     *       },
     *       "aggs": {
     *         "totalComStar": {
     *           "sum": {
     *             "field": "comments.stars"
     *           }
     *         }
     *       }
     *     }
     *   }
     * }
     * 级联搜索
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse nested(String idx, Comment comment) throws IOException {
        SearchRequest searchRequest = new SearchRequest(idx);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        MatchQueryBuilder matchName = QueryBuilders.matchQuery("comments.name", comment.getName());
        MatchQueryBuilder matchAge = QueryBuilders.matchQuery("comments.age",comment.getAge());
        BoolQueryBuilder subBool = QueryBuilders.boolQuery().must(matchName).must(matchAge);

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(QueryBuilders.nestedQuery("comments", subBool, ScoreMode.None));

        searchSourceBuilder.query(boolQuery);

        searchSourceBuilder
                .aggregation(AggregationBuilders.nested("mycomments","comments")
                        .subAggregation(AggregationBuilders.sum("totalComStar").field("comments.stars"))
                );

        searchRequest.source(searchSourceBuilder);

        return highClient.search(searchRequest,RequestOptions.DEFAULT);
    }
}
